namespace CrossFlow.GraphLib
{
  using System;
  using System.Collections.Generic;
  using System.Text;

  [Serializable]
  public abstract class Node
  {
    private string itsName;
    private Guid itsUUID;
    private object itsData;
    private DirectedGraph itsContainerGraph;

    internal Node(string name, object data)
    {
      itsUUID = Guid.NewGuid();
      Name = name;
      itsData = data;
    }
    public Node(string name) : this(name, null) { }

    public string Name
    {
      get { return itsName; }
      set { itsName = string.IsNullOrEmpty(value) ? itsUUID.ToString() : value; }
    }
    public Guid GUID
    {
      get { return itsUUID; }
      internal set { itsUUID = value; }
    }
    public object Data
    {
      get { return itsData; }
      set { itsData = value; }
    }
    public DirectedGraph ContainerGraph
    {
      get { return itsContainerGraph; }
      internal protected set { itsContainerGraph = value; }
    }
    internal abstract void AddTo(DirectedGraph dag);
  }

  [Serializable]
  public class Vertex : Node
  {
    protected Dictionary<Guid, Edge> itsEdges;
    protected Dictionary<Guid, Edge> itsOutgoingEdges;
    protected Dictionary<Guid, Edge> itsIncomingEdges;

    public Vertex(string name) : this(name, null) { }
    public Vertex(string name, object data) : base(name, data)
    {
      itsEdges = new Dictionary<Guid, Edge>();
      itsOutgoingEdges = new Dictionary<Guid, Edge>();
      itsIncomingEdges = new Dictionary<Guid, Edge>();
    }

    //scaffold prior to design proper traverse strategy
    /*public Edge this[int index]
    {
      get
      {
        int count = 0;
        foreach (Edge e in itsEdges.Values)
        {
          if (index == count)
            return e;
        }
        throw new ArgumentOutOfRangeException("index","No such index[" + index + "] on Edge set");
      }
    }*/

    public Vertex this[string vertex_name]
    {
      get
      {
        foreach (Edge e in this.itsOutgoingEdges.Values)
          if (e.Target.Name == vertex_name) return e.Target;
        return null;
      }
    }

    public int EdgeCount { get { return this.itsEdges.Count; } }
    public int IncomingEdgeCount { get { return this.itsIncomingEdges.Count; } }
    public int OutgoingEdgeCount { get { return this.itsOutgoingEdges.Count; } }
    public virtual bool IsComposite { get { return false; } }

    public IEnumerable<Edge> Edges
    {
      get
      {
        foreach (Edge e in this.itsEdges.Values)
          yield return e;
      }
    }

    public IEnumerable<Edge> OutgoingEdges
    {
      get
      {
        foreach (Edge e in this.itsOutgoingEdges.Values)
          yield return e;
      }
    }

    public IEnumerable<Edge> IncomingEdges
    {
      get
      {
        foreach (Edge e in this.itsIncomingEdges.Values)
          yield return e;
      }
    }

    internal void AddOutgoingEdge(Edge e)
    {
      itsOutgoingEdges.Add(e.GUID, e);
      AddEdge(e);
    }
    internal void AddIncomingEdge(Edge e)
    {
      itsIncomingEdges.Add(e.GUID, e);
      AddEdge(e);
    }
    internal void AddEdge(Edge e)
    {
      itsEdges[e.GUID] = e; //TODO:Test circularity
      //			itsEdges.Add(e.GUID, e);
    }
    internal override void AddTo(DirectedGraph dag)
    {
      dag.AddVertex(this);
    }
    internal virtual void Leave() { }
    internal virtual void Enter() { }
  }

  [Serializable]
  public class Edge : Node
  {
    private Vertex itsSource;
    private Vertex itsTarget;

    public Edge(Vertex s, Vertex t) : this(string.Empty, s, t, null) { }
    public Edge(string name, Vertex s, Vertex t) : this(name, s, t, null) { }
    public Edge(string name, Vertex s, Vertex t, object data) : base(name, data)
    {
      itsSource = s;
      itsTarget = t;
      itsSource.AddOutgoingEdge(this);
      itsTarget.AddIncomingEdge(this);
    }

    public Vertex Source
    {
      get { return itsSource; }
      set { itsSource = value; }
    }
    public Vertex Target
    {
      get { return itsTarget; }
      set { itsTarget = value; }
    }

    internal override void AddTo(DirectedGraph dag)
    {
      dag.AddEdge(this);
    }
  }

  /*
    [Serializable]
    public class CompositeVertex : Vertex
    { 
      public CompositeVertex(string name) : base(name){}
      public CompositeVertex(string name, object data) : base(name, data) {}
    }
  */
  [Serializable]
  public class SubDigraph : Vertex
  {
    private DirectedGraph itsInnerDigraph;

    public SubDigraph(string name) : this(name, null) { }
    public SubDigraph(string name, object data) : base(name, data)
    {
      this.itsInnerDigraph = new DirectedGraph();
    }
    public SubDigraph(string name, DirectedGraph graph, object data) : base(name, data)
    {
      if (graph == null) throw new ArgumentNullException("graph");
      this.itsInnerDigraph = graph;
    }

    public DirectedGraph InnerDigraph
    {
      get { return this.itsInnerDigraph; }
      set { this.itsInnerDigraph = value; }
    }

    public override bool IsComposite { get { return true; } }
  }

  [Serializable]
  public class DirectedGraph : Node
  {
    private Dictionary<string, Vertex> itsVertexes;
    private Dictionary<string, Edge> itsEdges;
    private int itsNodeCount;
    private Vertex itsInitialVertex;

    internal DirectedGraph() : this(string.Empty) { }
    public DirectedGraph(string name) : this(name, null) { }
    public DirectedGraph(string name, object data) : base(name,data)
    {
      itsVertexes = new Dictionary<string, Vertex>();
      itsEdges = new Dictionary<string, Edge>();
    }

    public Vertex InitialVertex
    {
      get { return itsInitialVertex; }
      protected set { itsInitialVertex = value; }
    }
    public int NodeCount { get { return itsNodeCount; } }
    public int VertexCount { get { return itsVertexes.Count; } }
    public int EdgeCount { get { return itsEdges.Count; } }

    public IEnumerable<Vertex> Vertexes
    {
      get
      {
        foreach (Vertex v in this.itsVertexes.Values)
          yield return v;
      }
    }

    public IEnumerable<Edge> Edges
    {
      get
      {
        foreach (Edge e in this.itsEdges.Values)
          yield return e;
      }
    }


    public bool ContainsVertex(string name)
    {
      return this.itsVertexes.ContainsKey(name);
    }

    public Vertex this[string name]
    {
      get
      {
        if (this.itsVertexes.ContainsKey(name))
          return this.itsVertexes[name];
        throw new InvalidOperationException("There is no vertex with name:" + name);
      }
    }

    public void AddNodes(params Node[] nodes)
    {
      foreach (Node n in nodes)
      {
        n.ContainerGraph = this;
        n.AddTo(this);
      }
    }

    internal override void AddTo(DirectedGraph dag) { }

    internal void AddVertex(Vertex v)
    {
      if (itsInitialVertex == null)
      {
        itsInitialVertex = v;
        OnSetInitialVertex();
      }
      itsVertexes.Add(v.Name, v);
      ++itsNodeCount;
    }
    internal void AddEdge(Edge e)
    {
      itsEdges.Add(e.Name, e);
      ++itsNodeCount;
    }
    protected virtual void OnSetInitialVertex() { }
  }
}